package com.linkgie.galaxyframework.crypto.context;

import java.util.Base64;
import java.util.Objects;

import com.linkgie.galaxyframework.crypto.Algorithm;
import com.linkgie.galaxyframework.crypto.Cryptogram;
import com.linkgie.galaxyframework.crypto.HashCode;
import com.linkgie.galaxyframework.crypto.PrivateKey;
import com.linkgie.galaxyframework.crypto.PublicKey;
import com.linkgie.galaxyframework.crypto.Signature;
import com.linkgie.galaxyframework.crypto.SymmetricKey;
import com.linkgie.galaxyframework.crypto.spi.Cryptobytes;
import com.linkgie.galaxyframework.crypto.spi.Hashbytes;
import com.linkgie.galaxyframework.crypto.spi.PrivateKeybytes;
import com.linkgie.galaxyframework.crypto.spi.PublicKeybytes;
import com.linkgie.galaxyframework.crypto.spi.Signaturebytes;
import com.linkgie.galaxyframework.crypto.spi.SymmetricKeybytes;
import com.linkgie.galaxyframework.utils.Base58Encoding;

public class CryptogramEncoding {

	private Encoding ENCODING;

	public CryptogramEncoding(Encoding encoding) {
		this.ENCODING = encoding;
	}

	public Cryptogram encode(Algorithm algorithm, Cryptobytes cryptoBytes) {
		if (cryptoBytes instanceof Hashbytes) {
			return encode(algorithm, (Hashbytes) cryptoBytes);

		} else if (cryptoBytes instanceof Signaturebytes) {
			return encode(algorithm, (Signaturebytes) cryptoBytes);

		} else if (cryptoBytes instanceof PrivateKeybytes) {
			return encode(algorithm, (PrivateKeybytes) cryptoBytes);

		} else if (cryptoBytes instanceof PublicKeybytes) {
			return encode(algorithm, (PublicKeybytes) cryptoBytes);

		} else if (cryptoBytes instanceof SymmetricKeybytes) {
			return encode(algorithm, (SymmetricKeybytes) cryptoBytes);
		} else {
			throw new IllegalStateException("Illegal type of crypto bytes! --" + cryptoBytes.getClass().getName());
		}
	}

	public HashCode encode(Algorithm algorithm, Hashbytes cryptoBytes) {
		return new HashCodeEntry(algorithm, cryptoBytes);
	}

	public Signature encode(Algorithm algorithm, Signaturebytes cryptoBytes) {
		return new SignatureEntry(algorithm, cryptoBytes);
	}

	public PrivateKey encode(Algorithm algorithm, PrivateKeybytes cryptoBytes) {
		return new PrivateKeyEntry(algorithm, cryptoBytes);
	}

	public PublicKey encode(Algorithm algorithm, PublicKeybytes cryptoBytes) {
		return new PublicKeyEntry(algorithm, cryptoBytes);
	}

	public SymmetricKey encode(Algorithm algorithm, SymmetricKeybytes cryptoBytes) {
		return new SymmetricKeyEntry(algorithm, cryptoBytes);
	}

	/**
	 * 编码为字节数组；
	 * 
	 * @param algorithm
	 * @param bytesContent 密码内容；等效于 {@link Cryptobytes#getEncodedBytes()} 的结果；
	 * @return
	 */
	public byte[] encodeToBytes(Algorithm algorithm, byte[] bytesContent) {
		return ENCODING.encode(algorithm, bytesContent);
	}

	public Hashbytes decode(HashCode hashCode) {
		if (hashCode instanceof HashCodeEntry) {
			return ((HashCodeEntry) hashCode).cryptoBytes;
		}
		throw new IllegalArgumentException("Illegal hash code object that is not created by crypto service!");
	}

	public Signaturebytes decode(Signature signatue) {
		if (signatue instanceof SignatureEntry) {
			return ((SignatureEntry) signatue).cryptoBytes;
		}
		throw new IllegalArgumentException("Illegal hash code object that is not created by crypto service!");
	}

	public PrivateKeybytes decode(PrivateKey privateKey) {
		if (privateKey instanceof PrivateKeyEntry) {
			return ((PrivateKeyEntry) privateKey).cryptoBytes;
		}
		throw new IllegalArgumentException("Illegal hash code object that is not created by crypto service!");

	}

	public PublicKeybytes decode(PublicKey publicKey) {
		if (publicKey instanceof PublicKeyEntry) {
			return ((PublicKeyEntry) publicKey).cryptoBytes;
		}
		throw new IllegalArgumentException("Illegal hash code object that is not created by crypto service!");

	}

	public SymmetricKeybytes decode(SymmetricKey symmetricKey) {
		if (symmetricKey instanceof SymmetricKeyEntry) {
			return ((SymmetricKeyEntry) symmetricKey).cryptoBytes;
		}
		throw new IllegalArgumentException("Illegal hash code object that is not created by crypto service!");

	}

	private class CryptogramEntry<T extends Cryptobytes> implements Cryptogram {

		protected final Algorithm algorithm;

		protected T cryptoBytes;

		public CryptogramEntry(Algorithm algorithm, T cryptoBytes) {
			this.algorithm = algorithm;
			this.cryptoBytes = cryptoBytes;
		}

		@Override
		public Algorithm getAlgorithm() {
			return algorithm;
		}

		@Override
		public byte[] getEncoded() {
			return ENCODING.encode(algorithm, cryptoBytes);
		}

		@Override
		public String getEncodedString() {
			return Base58Encoding.encode(getEncoded());
		}
		
		@Override
		public byte[] getRaw() {
			return cryptoBytes.getRawBytes();
		}
		
		@Override
		public String getRawBase58() {
			return Base58Encoding.encode(getRaw());
		}

		@Override
		public String getRawBase64() {
			return Base64.getEncoder().encodeToString(getRaw());
		}

		@Override
		public String toString() {
			return getEncodedString();
		}

		@Override
		public boolean equals(Object obj) {
			if (obj == null) {
				return false;
			}
			if (obj instanceof CryptogramEntry) {
				CryptogramEntry<?> other = (CryptogramEntry<?>) obj;
				return this.algorithm.getCode() == other.getAlgorithm().getCode() && Objects.equals(this.cryptoBytes, other.cryptoBytes);
			}
			return false;
		}
	}

	private class HashCodeEntry extends CryptogramEntry<Hashbytes> implements HashCode {

		public HashCodeEntry(Algorithm algorithm, Hashbytes cryptoBytes) {
			super(algorithm, cryptoBytes);
		}
	}

	private class SignatureEntry extends CryptogramEntry<Signaturebytes> implements Signature {

		public SignatureEntry(Algorithm algorithm, Signaturebytes cryptoBytes) {
			super(algorithm, cryptoBytes);
		}
	}

	private class PrivateKeyEntry extends CryptogramEntry<PrivateKeybytes> implements PrivateKey {

		public PrivateKeyEntry(Algorithm algorithm, PrivateKeybytes cryptoBytes) {
			super(algorithm, cryptoBytes);
		}
	}

	private class PublicKeyEntry extends CryptogramEntry<PublicKeybytes> implements PublicKey {

		public PublicKeyEntry(Algorithm algorithm, PublicKeybytes cryptoBytes) {
			super(algorithm, cryptoBytes);
		}
	}

	private class SymmetricKeyEntry extends CryptogramEntry<SymmetricKeybytes> implements SymmetricKey {

		public SymmetricKeyEntry(Algorithm algorithm, SymmetricKeybytes cryptoBytes) {
			super(algorithm, cryptoBytes);
		}
	}

}
